import { CheckUtil } from './check';

/** 精确数值计算相关包 **/
export const MathUtil = {
  _boundaryCheckingState: true,

  /**
   * 是否进行数值越界检查，默认开启
   * @param flag
   */
  enableBoundaryChecking(flag) {
    this._boundaryCheckingState = !!flag;
  },

  /**
   * 检测数字是否越界，如果越界给出提示
   * @param num
   */
  checkBoundary(num) {
    if (this._boundaryCheckingState) {
      if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
        // console.warn(
        //   `${num} is beyond boundary when transfer to integer, the results may not be accurate!`
        // );
      }
    }
  },

  /**
   * 修正浮点数计算后精度丢失问题
   * @example 0.1+0.2=0.30000000000000004 => 0.1+0.2=0.3
   * @param num {number|string}
   * @param precision
   * @returns {number}
   */
  strip(num, precision = 15) {
    if (!CheckUtil.isLikeNumber(num)) {
      return 0;
    }

    return +parseFloat(Number(num).toPrecision(precision));
  },

  /**
   * 获取数值的小数位长度
   * @param num
   * @returns {number}
   */
  digitLength(num) {
    if (!CheckUtil.isLikeNumber(num)) {
      return 0;
    }

    const eSplit = num.toString().split(/[eE]/);
    const len = (eSplit[0].split('.')[1] || '').length - +(eSplit[1] || 0);
    return len > 0 ? len : 0;
  },

  /**
   * 将浮点数转成整数（直接去掉小数点，非四舍五入）
   * @param num
   * @returns {number}
   */
  floatToInt(num) {
    if (!CheckUtil.isLikeNumber(num)) {
      return 0;
    }

    if (num.toString().indexOf('e') === -1) {
      return Number(num.toString().replace('.', ''));
    }
    const dLen = this.digitLength(num);
    return dLen > 0 ? this.strip(Number(num) * Math.pow(10, dLen)) : Number(num);
  },

  /**
   * 精确乘法（先将小数转换为整数相乘后，再除以转化成整数的数值）
   * @param num1
   * @param num2
   * @param others
   * @returns {number}
   */
  times(num1, num2, ...others) {
    if (!CheckUtil.isLikeNumber(num1) || !CheckUtil.isLikeNumber(num2)) {
      return 0;
    }

    if (others.length > 0) {
      return this.times(this.times(num1, num2), others[0], ...others.slice(1));
    }

    const num1Int = this.floatToInt(num1);
    const num2Int = this.floatToInt(num2);
    const baseNum = this.digitLength(num1) + this.digitLength(num2);
    const leftValue = num1Int * num2Int;

    this.checkBoundary(leftValue);

    return leftValue / Math.pow(10, baseNum);
  },

  /**
   * 精确除法（先将小数转换为整数相除后，再除以转化成整数的数值）
   * @param num1
   * @param num2
   * @param others
   * @returns {number}
   */
  divide(num1, num2, ...others) {
    if (!CheckUtil.isLikeNumber(num1) || !CheckUtil.isLikeNumber(num2)) {
      return 0;
    }

    if (others.length > 0) {
      return this.divide(this.divide(num1, num2), others[0], ...others.slice[1]);
    }

    const num1Int = this.floatToInt(num1);
    const num2Int = this.floatToInt(num2);
    this.checkBoundary(num1Int);
    this.checkBoundary(num2Int);
    // fix: 类似 10 ** -4 为 0.00009999999999999999，strip 修正
    return this.times(num1Int / num2Int, this.strip(Math.pow(10, this.digitLength(num2) - this.digitLength(num1))));
  },

  /**
   * 精确加法（先将小数转换为整数相加后，再除以转化成整数的数值）
   * @param num1
   * @param num2
   * @param others
   * @returns {number}
   */
  plus(num1, num2, ...others) {
    if (!CheckUtil.isLikeNumber(num1) || !CheckUtil.isLikeNumber(num2)) {
      return 0;
    }

    if (others.length > 0) {
      return this.plus(this.plus(num1, num2), others[0], ...others.slice(1));
    }

    const baseNum = Math.pow(10, Math.max(this.digitLength(num1), this.digitLength(num2)));
    return (this.times(num1, baseNum) + this.times(num2, baseNum)) / baseNum;
  },

  /**
   * 精确减法（先将小数转换为整数相减后，再除以转化成整数的数值）
   * @param num1
   * @param num2
   * @param others
   * @returns {number}
   */
  minus(num1, num2, ...others) {
    if (!CheckUtil.isLikeNumber(num1) || !CheckUtil.isLikeNumber(num2)) {
      return 0;
    }

    if (others.length > 0) {
      return this.minus(this.minus(num1, num2), others[0], ...others.slice(1));
    }

    const baseNum = Math.pow(10, Math.max(this.digitLength(num1), this.digitLength(num2)));
    return (this.times(num1, baseNum) - this.times(num2, baseNum)) / baseNum;
  },

  /**
   * 四舍五入，可选择保留小数位
   * @param num
   * @param ratio <保留小数位>
   * @returns {number}
   */
  round(num, ratio) {
    if (!CheckUtil.isLikeNumber(num)) {
      return 0;
    }

    const base = Math.pow(10, ratio);
    return this.divide(Math.round(this.times(num, base)), base);
  },

  /**
   * 相加后保留小数位（默认2位）
   * @param numbers 数值数组
   * @param ratio
   * @returns {number}
   */
  plusRound({ numbers, ratio = 2 }) {
    return this.round(this.plus(...numbers), ratio);
  },

  /**
   * 相减后保留小数位（默认2位）
   * @param numbers 数值数组
   * @param ratio
   * @returns {number}
   */
  minusRound({ numbers, ratio = 2 }) {
    return this.round(this.minus(...numbers), ratio);
  },

  /**
   * 相乘后保留小数位（默认2位）
   * @param numbers 数值数组
   * @param ratio
   * @returns {number}
   */
  timesRound({ numbers, ratio = 2 }) {
    return this.round(this.times(...numbers), ratio);
  },

  /**
   * 相除后保留小数位（默认2位）
   * @param numbers 数值数组
   * @param ratio
   * @returns {number}
   */
  divideRound({ numbers, ratio = 2 }) {
    return this.round(this.divide(...numbers), ratio);
  },

  /**
   * 生成范围内的随机整数
   * @param min
   * @param max
   * @returns {*}
   */
  randomNum(min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
  },
};
